package com.ruoyi.common.utils;


import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @author hlf
 * @createTime 2025/2/17 15:21
 * @description 分布式锁工具类
 */

@Slf4j
@Component
public class RedisDistributedLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 锁前缀
    private static final String LOCK_PREFIX = "lock:";
    // 锁过期时间，单位：毫秒
    private static final long LOCK_EXPIRE_TIME = 30000; // 30秒
    // 默认重试次数
    private static final int DEFAULT_RETRY_COUNT = 3;
    // 默认重试间隔时间，单位：毫秒
    private static final long DEFAULT_RETRY_INTERVAL = 1000; // 1秒

    private static final long RENEWAL_INTERVAL = 20 * 1000; // 锁续期间隔时间 20s


    /**
     * 尝试获取分布式锁（不等待，自旋）
     *
     * @param lockKey   锁的key
     * @param requestId 请求标识（可以使用UUID）
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId) {
        String key = LOCK_PREFIX + lockKey;
        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, requestId, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        return success != null && success;
    }

    /**
     * 尝试获取分布式锁（带重试机制）
     *
     * @param lockKey       锁的key
     * @param requestId     请求标识（可以使用UUID）
     * @param retryCount    重试次数
     * @param retryInterval 重试间隔时间，单位：毫秒
     * @return 是否获取成功
     */
    public boolean tryLockWithRetry(String lockKey, String requestId, int retryCount, long retryInterval) {
        String key = LOCK_PREFIX + lockKey;
        int attempts = 0;
        while (attempts < retryCount) {
            Boolean success = redisTemplate.opsForValue().setIfAbsent(key, requestId, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
            if (success != null && success) {
                return true; // 锁获取成功
            }
            // 如果获取锁失败，等待一段时间后重试
            attempts++;
            try {
                TimeUnit.MILLISECONDS.sleep(retryInterval);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return false; // 超过重试次数仍未获取锁
    }

    /**
     * 释放分布式锁
     * 使用 Lua 脚本确保原子性，只有当锁的值和当前线程的 lockValue 匹配时才删除锁
     *
     * @param lockKey   锁的key
     * @param requestId 请求标识 uuid
     * @param lockKey   锁的 key
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        String key = LOCK_PREFIX + lockKey;
        String luaScript =
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                        "   return redis.call('del', KEYS[1]) " +
                        "else " +
                        "   return 0 " +
                        "end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);

        Long result = redisTemplate.execute(
                redisScript,
                Collections.singletonList(key),
                requestId
        );
        return result == 1 ? true : false;
    }

    /**
     * 默认重试机制（重试3次，每次间隔1秒）
     *
     * @param lockKey   锁的key
     * @param requestId 请求标识
     * @return 是否获取成功
     */
    public boolean tryLockWithDefaultRetry(String lockKey, String requestId) {
        return tryLockWithRetry(lockKey, requestId, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_INTERVAL);
    }


    /**
     * 启动看门狗定期续期锁
     */
    public void startWatchdog(String lockKey, String requestId) {
        ThreadPoolTaskExecutor executor = SpringUtils.getBean("threadPoolTaskExecutor");
        // 启动后台线程进行锁续期
        executor.execute(() -> {
            while (true) {
                try {
                    // 每隔一段时间更新锁的过期时间，确保锁没有过期
                    Thread.sleep(RENEWAL_INTERVAL);
                    // 检查锁是否存在以及是否为当前请求的锁
                    String currentValue = redisTemplate.opsForValue().get(LOCK_PREFIX + lockKey);
                    if (currentValue != null && currentValue.equals(requestId)) {
                        // 续期
                        redisTemplate.opsForValue().set(LOCK_PREFIX + lockKey, requestId, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
                        log.info("续期成功");
                    } else {
                        break;  // 锁已经过期或被其他线程释放，退出
                    }
                } catch (InterruptedException e) {
                    break;  // 如果线程被中断，则停止续期
                }
            }
        });
    }


}